﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using AmaroK86.MassEffect3.PCC;
using Gibbed.IO;

namespace AmaroK86.MassEffect3.UnrealObjects
{
    public class UnrealObject // this is still a very unstable class, only fully tested with Texture2D derivation
    {
        public PCCObject.ExportEntry exportRef { get; protected set; }
        public PCCObject pccRef { get { return exportRef.pccRef; } }
        protected byte[] headerData; // first values of export data (unknown data...)
        protected byte[] data;       // this is the remaining data bytes that goes AFTER properties. Use this for editing
        protected int dataOffset;

        public Properties properties;

        public UnrealObject(PCCObject.ExportEntry export)
        {
            exportRef = export;
            byte[] buffer = export.Data;

			// don't check for Unreal Classes
            if (export.ClassName == "Class")
                return;

            int headerSize = 0;
            int lastValue = 0;

			// do-while that guess where the properties begin
            do
            {
                if (pccRef.Names[lastValue] == exportRef.ObjectName)
                    headerSize += 4;

                if (BitConverter.ToInt32(buffer, 4) < 0)
                {
                    headerSize = 30;
                }
                else
                {
                    while (BitConverter.ToInt32(buffer, headerSize) == 0)
                        headerSize += 4;
                    while (BitConverter.ToInt32(buffer, headerSize) != 0)
                        headerSize += 4;

                    if (headerSize > 4)
                        headerSize -= 4;
                }
                headerData = new byte[headerSize];
                Buffer.BlockCopy(buffer, 0, headerData, 0, headerSize);
                lastValue = BitConverter.ToInt32(buffer, headerSize);
                //Console.WriteLine("firstsize: " + firstSize + " lastValue: " + lastValue + ", " + pccRef.Names[lastValue] + " != " + exportRef.ObjectName);
            }
            while (lastValue <= pccRef.Names.Count && lastValue >= 0 && pccRef.Names[lastValue] == exportRef.ObjectName);

            //fill the properties list
            if (lastValue < pccRef.Names.Count || lastValue > 0 || pccRef.Names[lastValue] != "None")
                properties = new Properties(pccRef, buffer, headerSize, out dataOffset);
            else
                properties = null;

            //fill data byte array
            data = new byte[buffer.Length - dataOffset];
            Buffer.BlockCopy(buffer, dataOffset, data, 0, data.Length);
        }

		// this function saves all data (header, properties and final data) inside original Export
        public virtual void commitChanges()
        {
            byte[] byteBuffer;
            MemoryStream buffer = new MemoryStream();

            //write unknown header data
            buffer.Write(headerData, 0, headerData.Length);
            //write properties
            byteBuffer = properties.ToByteArray();
            buffer.Write(byteBuffer, 0, byteBuffer.Length);
            //write data
            buffer.Write(data, 0, data.Length);

            exportRef.Data = buffer.ToArray();
        }
    }

    public class Properties : List<UnrealType> // Properties is a list of UnrealType objects
    {
        PCCObject pccRef;

        // this indexing allows easier access to data editing
        public UnrealType this[string searchName]
        {
            get
            {
                int idx = this.FindIndex(prop => prop.Name == searchName);
                if (idx != -1)
                    return this[idx];
                else
                    throw new KeyNotFoundException("Invalid property name \"" + searchName + "\"");
            }
            set
            {
                int idx = this.FindIndex(prop => prop.Name == searchName);
                if (idx != -1)
                    this[idx] = value;
                else
                    throw new KeyNotFoundException("Invalid property name \"" + searchName + "\"");
            }
        }

        public void Remove(string name)
        {
            if (exist(name))
            {
                this.RemoveAll(property => property.Name == name);
            }
        }

        public bool exist(string searchName)
        {
            try
            {
                object val = this[searchName].Value;
                return true;
            }
            catch
            {
                return false;
            }
        }

        public Properties(PCCObject pccRef, byte[] data, int startOffset, out int dataOffset)
        {
            this.pccRef = pccRef;

            MemoryStream buffer = new MemoryStream(data);
            buffer.Seek(startOffset, SeekOrigin.Begin);

            string propName, propType;
            int propSize;
            ulong propFlag;

            while (true)
            {
                UnrealType newProperty;
                int nameIdx = (int)buffer.ReadValueU64();
                propName = pccRef.Names[nameIdx];
				// if a None value has been found outside the properties means that there are no more
                if (propName == "None")
                {
                    /*if (buffer.Position == buffer.Length) // if there is no extra data after properties
                        dataOffset = -1;
                    else*/
                    dataOffset = (int)buffer.Position;

                    break; // exit the while loop
                }

                propType = pccRef.Names[(int)buffer.ReadValueU64()];

                propFlag = buffer.ReadValueU64();
                propSize = (int)(propFlag);

                switch (propType)
                {
                    case "IntProperty":
                        {
                            newProperty = new IntType(pccRef, propName, propFlag, buffer.ReadValueS32());
                            this.Add(newProperty);
                        }
                        break;
                    case "FloatProperty":
                        {
                            newProperty = new FloatType(pccRef, propName, propFlag, buffer.ReadValueF32());
                            this.Add(newProperty);
                        }
                        break;
                    case "BoolProperty":
                        {
                            newProperty = new BoolType(pccRef, propName, propFlag, buffer.ReadValueB8());
                            this.Add(newProperty);
                        }
                        break;
                    case "NameProperty":
                        {
                            newProperty = new NameType(pccRef, propName, propFlag, pccRef.Names[(int)buffer.ReadValueS64()]);
                            this.Add(newProperty);
                        }
                        break;
                    case "StringRefProperty":
                        {
                            newProperty = new StringRefType(pccRef, propName, propFlag, buffer.ReadValueS32());
                            this.Add(newProperty);
                        }
                        break;
                    case "ByteProperty":
                        {
                            if(propFlag == 1)
                                newProperty = new ByteType(pccRef, propName, propFlag, pccRef.Names[(int)buffer.ReadValueS64()], null, buffer.ReadValueS8());
                            else
                                newProperty = new ByteType(pccRef, propName, propFlag, pccRef.Names[(int)buffer.ReadValueS64()], pccRef.Names[buffer.ReadValueS32()], buffer.ReadValueS32());
                            newProperty.Name = propName;
                            newProperty.TypeName = propType;
                            this.Add(newProperty);
                        }
                        break;
                    case "ObjectProperty":
                        {
                            newProperty = new ObjectType(pccRef, propName, propFlag, buffer.ReadValueS32());
                            newProperty.Name = propName;
                            newProperty.TypeName = propType;
                            this.Add(newProperty);
                        }
                        break;
                    case "StrProperty":
                        {
                            int strLength = buffer.ReadValueS32();
                            string readString;
                            if (strLength < 0)
                                readString = buffer.ReadString(strLength * -2, true, Encoding.Unicode);
                            else
                                readString = buffer.ReadString(strLength, true, Encoding.ASCII);

                            newProperty = new StrType(pccRef, propName, propFlag, readString);
                            newProperty.Name = propName;
                            newProperty.TypeName = propType;
                            this.Add(newProperty);
                        }
                        break;
                    case "ArrayProperty": // need more implementation, now it doesn't add it to property list...
                        {
                            int arrayBegPos = (int)buffer.Position;

                            int numOfArrElements = buffer.ReadValueS32();
                            buffer.Seek(arrayBegPos + propSize, SeekOrigin.Begin);
                        }
                        break;
                    case "DelegateProperty": // need more implementation, now it doesn't add it to property list...
                        buffer.Seek(propSize, SeekOrigin.Current);
                        break;
                    case "StructProperty": // just copy raw bytes...
                        {
                            string structName = pccRef.Names[(int)buffer.ReadValueS64()];
                            byte[] structBuffer = new byte[propSize];
                            buffer.Read(structBuffer, 0, structBuffer.Length);
                            newProperty = new StructType(pccRef, propName, propFlag, structName, structBuffer);
                            this.Add(newProperty);
                        }
                        break;
                    default:
                        throw new NotImplementedException();
                }

            }
        }

		// writes all properties to a byte array
        public virtual byte[] ToByteArray()
        {
            MemoryStream buffer = new MemoryStream();
            foreach(UnrealType property in this)
            {
                byte[] propBuffer = property.ToArray();
                buffer.Write(propBuffer, 0, (int)propBuffer.Length);
            }
            buffer.WriteValueU64((ulong)pccRef.Names.IndexOf("None"));
            return buffer.ToArray();
        }
    }
}
